Seattle Parks & Neighborhoods - DRAFT

SciCloj logo
This is part of the Scicloj Clojure Data Scrapbook.

1 Draft

(This notebook will be divided into a few book chapters.)

Choosing where to live depends on many factors such as job opportunities and cost of living. I like walking, so one factor that is important to me is access to parks. In this analysis we’ll rank neighborhoods by park area proportional to total area. This article demonstrates how to prepare the geospatial data, calculate the value we want, and how to explore the meaning behind the numbers.

(ns index
  (:require [geo
             [geohash :as geohash]
             [jts :as jts]
             [spatial :as spatial]
             [io :as geoio]
             [crs :as crs]]
            [tech.v3.datatype.functional :as fun]
            [tablecloth.api :as tc]
            [scicloj.kindly.v4.kind :as kind]
            [scicloj.kindly.v4.api :as kindly]
            [hiccup.core :as hiccup]
            [charred.api :as charred]
            [clojure2d.color :as color]
            [clojure.string :as str])
  (:import (org.locationtech.jts.index.strtree STRtree)
           (org.locationtech.jts.geom Geometry Point Polygon Coordinate)
           (org.locationtech.jts.geom.prep PreparedGeometry
                                           PreparedLineString
                                           PreparedPolygon
                                           PreparedGeometryFactory)
           (java.util TreeMap)))

1.1 Gathering geospatial data

Both the neighborhood geometry and park geometry can be downloaded from Seattle GeoData:

I’ve saved a snapshot in the data directory.

The data format is gzipped GeoJSON. Java has a built-in class for handling gzip streams, and we’ll use the factual/geojson library to parse the string representation.

(defn slurp-gzip
  "Read a gzipped file into a string"
  [path]
  (with-open [in (java.util.zip.GZIPInputStream. (clojure.java.io/input-stream path))]
    (slurp in)))

Now we can conveniently load the data files we downloaded previously.

(defonce neighborhoods-geojson
  (slurp-gzip "data/Seattle/Neighborhood_Map_Atlas_Neighborhoods.geojson.gz"))
(def neighborhoods-features
  (geoio/read-geojson neighborhoods-geojson))

Let’s check that we got some data.

(count neighborhoods-features)
94

This seems like a reasonable number of neighborhoods.

Each member of the dataset is called a Feature. Here is one:

(-> neighborhoods-features
    first
    kind/pprint)
{:properties
 {:OBJECTID 27,
  :L_HOOD "Ballard",
  :S_HOOD "Loyal Heights",
  :S_HOOD_ALT_NAMES nil,
  :Shape__Area 2.13206555455933E7,
  :Shape__Length 18831.00959637},
 :geometry
 #object[org.locationtech.jts.geom.Polygon 0x14f89e26 "POLYGON ((-122.376336564723 47.6759176989664, -122.376707907517 47.6759982290459, -122.377903057631 47.6759978849021, -122.379988625303 47.6759893429385, -122.38182788443 47.675992079457, -122.383602752477 47.675979810424, -122.385435721425 47.6759587956618, -122.387087434206 47.6759521343769, -122.387702391216 47.6759476875929, -122.388955759168 47.6759424488678, -122.390449346263 47.6759259868868, -122.391773109273 47.6759158131504, -122.392827172066 47.6759153143353, -122.392956550545 47.6759141859541, -122.392956303464 47.6763287099834, -122.392959618409 47.676929558447, -122.392961263359 47.6775085790216, -122.392971502986 47.6781034200237, -122.392975009807 47.6792517755972, -122.392983771129 47.6798142464259, -122.39300195772 47.6811303942788, -122.393014530791 47.6824285808909, -122.393016158048 47.6837341155494, -122.393022827056 47.685021586284, -122.393030577589 47.6863450742964, -122.393032746621 47.687668638629, -122.393046187318 47.6889956475812, -122.393010665051 47.6902980913372, -122.393002415356 47.6905813205566, -122.392432373275 47.6905826938111, -122.390347910813 47.6905631204492, -122.388192979124 47.6905805029145, -122.385832831602 47.6905766041982, -122.383275316007 47.690593164597, -122.383201215024 47.6905936585447, -122.381453020656 47.6905978829044, -122.379876313408 47.6905988541749, -122.379103841332 47.6906041929746, -122.377117910432 47.6906029639028, -122.376810344766 47.6906051591463, -122.376810264834 47.6905341242701, -122.376810989127 47.6900117143006, -122.376803067488 47.688976146724, -122.376794751014 47.6879443550321, -122.376800402808 47.6870068457338, -122.376784403749 47.6860923366759, -122.376785260609 47.6851813698577, -122.376780535885 47.6842704786057, -122.376787310842 47.6833707854178, -122.376777423242 47.6822860591882, -122.376767820487 47.6811938310584, -122.376770442503 47.680154307228, -122.376770065639 47.6792017979916, -122.376769882216 47.6780678404081, -122.376759750881 47.6775387646826, -122.376761985459 47.676862137338, -122.376772171763 47.6764361697665, -122.376772451106 47.6764114022001, -122.376770700102 47.6763866189683, -122.37676692258 47.6763619488292, -122.376761120573 47.6763374777451, -122.376753802121 47.6763131984825, -122.376744969247 47.6762891966531, -122.376733608705 47.6762655291531, -122.376721246367 47.6762423037704, -122.376706358898 47.6762194979699, -122.376690473449 47.6761972626929, -122.376672575492 47.6761756116819, -122.376653693551 47.6761550023704, -122.376620811506 47.6761243856628, -122.37659791092 47.6761051588887, -122.376573006221 47.6760868163511, -122.376547112434 47.6760693435982, -122.376519722058 47.6760527482053, -122.376336564723 47.6759176989664))"]}

Each feature, in our case, represents a geographic region with a geometry and some properties.

And similarly for the parks:

(defonce parks-geojson
  (slurp-gzip "data/Seattle/Park_Boundary_(details).geojson.gz"))
(def parks-features
  (geoio/read-geojson parks-geojson))
(count parks-features)
2809

There are more parks than neighborhoods, which sounds right.

(delay
  (-> parks-features
      first
      kind/pprint))
{:properties
 {:SE_ANNO_CAD_DATA "",
  :NAMEFLAG 9,
  :ADDRESS " ",
  :MAINT "DPR",
  :LEASE "N",
  :PMA_NAME "East Duwamish GS: S Chicago St",
  :SUBPARCEL 9851,
  :PMA 442,
  :REVIEW_DATE "2004-04-08T00:00:00Z",
  :GlobalID "{D4B7025E-1135-4232-88AB-CBF72932E6AE}",
  :OBJECTID 207882,
  :AMWOID "PROPERTY-EDUWSC",
  :SDQL "QL-D1",
  :USE_ nil,
  :PIN "4006000485",
  :GIS_EDT_DT "2024-01-19T14:07:23Z",
  :SHAPE_Area 1.4691943312522366E-6,
  :SHAPE_Length 0.005215887160444538,
  :GIS_CRT_DT "2024-01-19T14:07:23Z",
  :NAME "EAST DUWAMISH GREENBELT",
  :OWNER "DPR",
  :ACQ_DATE "1997-10-21T00:00:00Z"},
 :geometry
 #object[org.locationtech.jts.geom.MultiPolygon 0x27028c0a "MULTIPOLYGON (((-122.28235899499998 47.525165833000074, -122.28168484899999 47.525164389000054, -122.28168307199996 47.52516438600003, -122.28145050399996 47.52516388600003, -122.281450582 47.525147437000044, -122.28092621899998 47.525146310000025, -122.28092721899998 47.52433074500004, -122.28129411599997 47.52433142700005, -122.28247185299995 47.52433360800006, -122.28270306299999 47.52433403400005, -122.28270162899997 47.52516656600005, -122.28235899499998 47.525165833000074)))"]}

And the parks are defined as geographic regions.

1.2 Drawing a map

Seattle coordinates

(def Seattle-center
  [47.608013 -122.335167])

The map we will create is A choropleth, though for now, we will use a fixed color, which is not so informative.

We will enrich every feature (e.g., neighborhood) with data relevant for its visual representation.

(defn enrich-feature [{:as   feature :keys [geometry]}
                      {:keys [tooltip-keys
                              style]}]
  (-> feature
      (update :properties
              (fn [properties]
                (-> properties
                    (assoc :tooltip (-> properties
                                        (select-keys tooltip-keys)
                                        (->> (map (fn [[k v]]
                                                    [:p [:b k] ":  " v]))
                                             (into [:div]))
                                        hiccup/html)
                           :style style))))))
(def neighborhoods-enriched-features
  (-> neighborhoods-geojson
      (charred/read-json {:key-fn keyword})
      :features
      (->> (mapv (fn [feature]
                   (-> feature
                       (enrich-feature
                        {:tooltip-keys [:L_HOOD :S_HOOD]
                         :style {:opacity     0.3
                                 :fillOpacity 0.1
                                 :color      "purple"
                                 :fillColor  "purple"}})))))))

Here is how we may generate a Choroplet map in Leaflet:

(defn choropleth-map [details]
  (delay
    (kind/reagent
     ['(fn [{:keys [provider
                    center
                    enriched-features]}]
         [:div
          {:style {:height "900px"}
           :ref   (fn [el]
                    (let [m (-> js/L
                                (.map el)
                                (.setView (clj->js center)
                                          11))]
                      (-> js/L
                          .-tileLayer
                          (.provider provider)
                          (.addTo m))
                      (-> js/L
                          (.geoJson (clj->js enriched-features)
                                    (clj->js {:style (fn [feature]
                                                       (-> feature
                                                           .-properties
                                                           .-style))}))
                          (.bindTooltip (fn [layer]
                                          (-> layer
                                              .-feature
                                              .-properties
                                              .-tooltip)))
                          (.addTo m))))}])
      details]
     {:reagent/deps [:leaflet]})))

We pick a tile layer provider from leaflet-providers.

(defn Seattle-choropleth-map [enriched-features]
  (choropleth-map
   {:provider "OpenStreetMap.Mapnik"
    :center     Seattle-center
    :enriched-features enriched-features}))

For our basic neighborhoods map:

(delay
  (Seattle-choropleth-map
   neighborhoods-enriched-features))

Now, let us see the parks:

(def parks-enriched-features
  (-> parks-geojson
      (charred/read-json {:key-fn keyword})
      :features
      (->> (mapv (fn [feature]
                   (-> feature
                       (enrich-feature
                        {:tooltip-keys [:PMA_NAME :NAME]
                         :style {:opacity     0.3
                                 :fillOpacity 0.1
                                 :color      "darkgreen"
                                 :fillColor  "darkgreen"}})))))))

1.3 Coordinate conversions

Both datasets use the WGS84 coordinate system.

EPSG:4326

For area computations we need to convert them to a coordinate system which is locally correct in terms of distances in a region around Seattle.

EPSG:2285

United States (USA) - Oregon and Washington. [1 3]:

Center coordinates Projected bounds WGS84 bounds
[1692592.39 -541752.55] [[559165.71 -1834123.81] [2832684.98 777497.1]] [[-124.79 41.98] [-116.47 49.05]]
(def crs-transform
  (geo.crs/create-transform (geo.crs/create-crs 4326)
                            (geo.crs/create-crs 2285)))
(defn wgs84->Seattle
  "Transforming latitude-longitude coordinates
  to local Euclidean coordinates around Seattle."
  [geometry]
  (geo.jts/transform-geom geometry crs-transform))

1.4 Some geometrical functions

(defn area [geometry]
  (.getArea geometry))
(defn buffer [geometry radius]
  (.buffer geometry radius))
(defn intersection [geometry1 geometry2]
  (.intersection geometry1 geometry2))

1.5 Geometry datasets

(defn geojson->dataset [geojson dataset-name]
  (-> geojson
      (->> (map (fn [{:keys [geometry properties]}]
                  (assoc properties :geometry geometry))))
      tc/dataset
      (tc/map-columns :geometry [:geometry] wgs84->Seattle)
      (tc/set-dataset-name dataset-name)))
(def neighborhoods
  (-> neighborhoods-features
      (geojson->dataset "Seattle neighborhoods")
      (tc/add-column :feature neighborhoods-enriched-features)))
(delay
  (-> neighborhoods
      (tc/drop-columns [:geometry :feature])
      (kind/table {:element/max-height "600px"})))
:OBJECTID :L_HOOD :S_HOOD :S_HOOD_ALT_NAMES :Shape__Area :Shape__Length
27 Ballard Loyal Heights 2.13206555455933E7 18831.00959637
28 Ballard Ballard Adams 2.25521329700012E7 29926.3393002841
29 Ballard Whittier Heights 1.41956874919434E7 15934.4392663199
30 Ballard West Woodland 2.21993668730164E7 21789.6510873493
31 North Central Phinney Ridge Woodland Park 3.21231178777466E7 27120.7859379681
32 North Central Wallingford Meridian, Tangle Town, Northlake 4.20683426137695E7 33040.2469861198
33 North Central Fremont 2.65037565718689E7 25999.2849357187
34 North Central Green Lake 3.73146297898865E7 25387.7074962486
35 Northeast View Ridge 2.56129799440918E7 22657.0394732799
36 Northeast Ravenna University Village 3.67833915366516E7 31163.6208763331
37 Northeast Sand Point 1.92498571397095E7 21396.6958008648
38 Northeast Bryant 1.54850780333252E7 20928.0017243062
39 Northeast Windermere Hawthorne Hills 2.23639666230164E7 33332.8291968304
40 Northeast Laurelhurst 1.90517768183594E7 26489.2422321737
41 Northeast Roosevelt Fairview 2.1491594324707E7 22866.0797374364
42 University District University of Washington 2.55236751180115E7 30983.2126641678
43 Queen Anne East Queen Anne 1.93546995965576E7 21488.397378299
44 Queen Anne West Queen Anne 1.79749859528503E7 20631.7057292923
45 Queen Anne Lower Queen Anne Uptown, Seattle Center 1.75156244566956E7 20496.7289222394
46 Queen Anne North Queen Anne 2.9604288333252E7 26753.5542817429
47 Cascade Westlake 3645184.07894897 11017.3231739615
48 Cascade Eastlake 7426907.96139526 16672.5117837403
49 Cascade South Lake Union 1.6107105419342E7 29323.5462843847
50 Magnolia Lawton Park 5.5091525290863E7 38766.0673349107
51 Magnolia Briarcliff Carleton Park 2.65509365782471E7 26609.7912261135
52 Magnolia Southeast Magnolia 1.84692638882751E7 20223.1801501522
53 Central Area Madrona 1.38454828085938E7 17052.9427067693
54 Central Area Harrison/Denny-Blaine 1.36033956731873E7 17241.2743191595
55 Central Area Minor Central District, Squire Park 1.7933144708252E7 18241.5487859812
56 Central Area Leschi 1.80917413764954E7 20413.0560516882
57 Central Area Mann Central District, Garfield 1.11338288620605E7 17843.105758736
58 Central Area Atlantic Judkins Park, Jackson Place, Coleman 2.06649862726135E7 25426.2311626675
59 Downtown Pike-Market Pike Place Market 2343359.32580566 7326.58173024624
60 Downtown Belltown Denny Regrade 9845864.78594971 17836.7271544608
61 Downtown International District Chinatown, ID, Little Saigon 6169375.81655884 11772.3871410021
62 Downtown Central Business District Commercial Core, West Edge 9477398.61834717 18209.4628624588
63 Downtown First Hill 9517568.47363281 13225.2335159542
64 Downtown Yesler Terrace 5348937.24771118 9611.35354641661
65 Downtown Pioneer Square 7545979.0869751 17297.5867148564
66 Interbay Interbay 3.35039410921631E7 64207.5867611718
67 Greater Duwamish SODO 4.96031675905914E7 38520.9885110863
68 Greater Duwamish Georgetown 5.13050726461182E7 45963.6656769607
69 Greater Duwamish South Park 2.94234521522369E7 30433.4048686311
70 Greater Duwamish Harbor Island 1.78323007354431E7 24381.2966280601
71 West Seattle Seaview 1.8463061797821E7 23122.2640032568
72 West Seattle Gatewood Morgan Junction 2.28217074689484E7 23801.2647005122
73 West Seattle Arbor Heights 2.61973140573883E7 28162.4351386166
74 West Seattle Alki 2.45834691841736E7 45568.1378536906
75 West Seattle North Admiral Belvidere 4.85814163202972E7 30687.7844700837
76 West Seattle Fairmount Park 1.61539000976257E7 24955.7128414259
77 West Seattle Genesee Alaska Junction, West Seattle Junction 2.11649765813446E7 19870.8696089241
78 West Seattle Fauntleroy Endolyne, Arroyo Heights, Brace Point 3.38818638706665E7 42276.5317877774
79 Beacon Hill North Beacon Hill Jefferson Park 4.84759144557953E7 33865.4891739901
80 Beacon Hill Mid-Beacon Hill 5.26204707883301E7 40522.1829275323
81 Beacon Hill South Beacon Hill 3.84488268989563E7 38996.7151070849
82 Beacon Hill Holly Park New Holly 8444235.97859192 15848.3624185113
83 Rainier Valley Brighton 1.8366938275177E7 18911.3293490837
84 Rainier Valley Dunlap 1.96016012150879E7 20847.6531922997
85 Rainier Valley Rainier Beach 3.31282361414948E7 34965.6132100475
86 Rainier Valley Rainier View Lake Ridge 1.82485661794434E7 34791.8218147105
87 Rainier Valley Mount Baker North Rainier 2.89869952343292E7 26318.9819054871
88 Rainier Valley Columbia City Columbia Heights 3.74065764927979E7 35177.0608039675
89 Delridge Highland Park 3.98103334158325E7 31111.0630837714
90 Delridge North Delridge Avalon, Luna Park, Pigeon Point 2.94546565672607E7 23549.9313666304
91 Delridge Riverview 3.30382671929626E7 25917.1176391015
92 Delridge High Point 2.33430083368683E7 23180.3963596714
93 Delridge South Delridge White Center 1.79233162238464E7 23416.3405359794
94 Delridge Roxhill Westwood Village 1.87810598441162E7 22360.0472289145
95 Seward Park Seward Park Lakewood, Hillman City 4.38352790059662E7 52212.7868103531
96 Northeast Wedgwood 3.05997136261597E7 23840.2187274324
97 Capitol Hill Portage Bay Roanoke Park 4421635.26721191 10013.2739563989
98 Capitol Hill Montlake North Capitol Hill 2.1474786741394E7 46165.83098345
99 Capitol Hill Madison Park Broadmoor, Washington Park 2.46809502223511E7 26230.4347480002
100 Capitol Hill Broadway Pike/Pine 3.02392405212402E7 26851.9474418256
101 Capitol Hill Stevens Interlaken Park, Miller Park, Madison-Miller 2.60395920790405E7 25522.3456368768
102 Lake City Victory Heights 2.15127314890137E7 20753.0018992847
103 Lake City Matthews Beach 3.09809783014832E7 31369.8925404597
104 Lake City Meadowbrook 1.70348487789307E7 18770.2820324215
105 Lake City Olympic Hills 2.58367421641541E7 19993.8160570763
106 Lake City Cedar Park 2.04414858487244E7 22788.760382662
110 Northwest Broadview 4.81134497303162E7 34640.309665739
111 Northwest Bitter Lake 2.76903364438171E7 26446.2030540938
112 Northgate Haller Lake 4.72030810796204E7 29413.5534484151
113 Northgate Pinehurst Jackson Park 2.94388508770142E7 26171.2717608103
114 Northwest North Beach/Blue Ridge 3.10622649915771E7 32947.8059119899
115 Northgate Licton Springs North College Park 2.17047062611389E7 20132.8982458333
116 Northgate Maple Leaf Olympic View 3.43804707826233E7 25503.0574082691
117 Northwest Crown Hill 1.58657504196777E7 18997.8080379411
118 Northwest Greenwood 4.20445636731262E7 26501.5265911049
119 Ballard Sunset Hill Golden Gardens, Shilshole 2.43356204689941E7 29416.7300633571
121 University District University District 1.36862548569946E7 21581.6459789299
123 University District University Heights Cowen Park 1.03757480522461E7 15163.9069096811
124 Downtown Denny Triangle Denny Regrade 5128172.31045532 10574.1779826691
126 Greater Duwamish Industrial District 6.24341310114746E7 88995.7569839975
(def parks
  (-> parks-features
      (geojson->dataset "Seattle parks")
      ;; avoiding some [linestring pathologies](https://gis.stackexchange.com/questions/50399/fixing-non-noded-intersection-problem-using-postgis)
      (tc/map-columns :geometry [:geometry] #(buffer % 1))))
(delay
  (-> parks
      (tc/drop-columns [:geometry :feature])
      (tc/head 20)
      (kind/table {:element/max-height "600px"})))
:SE_ANNO_CAD_DATA :NAMEFLAG :ADDRESS :MAINT :LEASE :PMA_NAME :SUBPARCEL :PMA :REVIEW_DATE :GlobalID :OBJECTID :AMWOID :SDQL :USE_ :PIN :GIS_EDT_DT :SHAPE_Area :SHAPE_Length :GIS_CRT_DT :NAME :OWNER :ACQ_DATE
9 DPR N East Duwamish GS: S Chicago St 9851 442 2004-04-08T00:00:00Z {D4B7025E-1135-4232-88AB-CBF72932E6AE} 207882 PROPERTY-EDUWSC QL-D1 4006000485 2024-01-19T14:07:23Z 1.4691943312522366E-6 0.005215887160444538 2024-01-19T14:07:23Z EAST DUWAMISH GREENBELT DPR 1997-10-21T00:00:00Z
9 DPR N East Duwamish GS: S Chicago St 4779 443 2004-04-08T00:00:00Z {26B4F4BC-F9AE-42D4-B062-51387C6D9FFF} 207883 PROPERTY-EDUWC QL-D1 0603001220 2024-01-19T14:07:23Z 1.460570191594512E-7 0.0015461198840460832 2024-01-19T14:07:23Z EAST DUWAMISH GREENBELT DPR 1994-10-06T00:00:00Z
9 DPR N East Duwamish GS: S Chicago St 4711 443 2004-04-08T00:00:00Z {E97D7F7F-2666-4008-A905-2F416A66C2A1} 207884 PROPERTY-EDUWC QL-D1 0603001220 2024-01-19T14:07:23Z 9.743577447048599E-8 0.0013271653095071236 2024-01-19T14:07:23Z EAST DUWAMISH GREENBELT DPR 1994-10-06T00:00:00Z
9 DPR N East Duwamish GS: S Chicago St 4779 443 2004-04-08T00:00:00Z {C28A4700-BB4F-4C25-928E-57022F6D001B} 207885 PROPERTY-EDUWC QL-D1 0603001220 2024-01-19T14:07:23Z 4.87386722719263E-8 0.0011082207640129814 2024-01-19T14:07:23Z EAST DUWAMISH GREENBELT DPR 1994-10-06T00:00:00Z
9 DPR N East Duwamish GS: S Chicago St 4712 443 2004-04-08T00:00:00Z {4DB6A932-327A-4E2E-AC8A-C068D114AA61} 207886 PROPERTY-EDUWC QL-D1 0603001220 2024-01-19T14:07:23Z 9.757207624044397E-8 0.0013284113092379031 2024-01-19T14:07:23Z EAST DUWAMISH GREENBELT DPR 1994-10-06T00:00:00Z
9 DPR N Genesee Park and Playfield 4618 409 1899-12-30T00:00:01Z {AD2E47C9-323B-4DDE-8186-98A4FC79C3F8} 207887 PROPERTY-GENPK QL-D1 4154300585 2024-01-19T14:07:23Z 2.892289652506793E-6 0.007000440212562059 2024-01-19T14:07:23Z GENESEE PARK AND PLAYFIELD DPR 1996-06-03T00:00:00Z
9 5244 SW JACOBSEN ROAD DPR N Wolf Creek Ravine Natural Area 16397 278 2004-04-19T00:00:00Z {A7E54352-0DC3-47E4-B562-DAA0F18B78E2} 207888 PROPERTY-WLFCRAV QL-D1 5344200125 2024-01-19T14:07:23Z 6.25557675764001E-8 0.0010504796990982068 2024-01-19T14:07:23Z WOLF CREEK RAVINE NATURAL AREA DPR 2004-04-07T00:00:00Z
9 DPR N Jefferson Park 15550 114 2004-04-08T00:00:00Z {B82B7BD2-DCD9-49A2-BE5D-30C1D9BF4392} 207889 PROPERTY-JEFFPK QL-D1 1624049270 2024-01-19T14:07:23Z 3.2670040661028276E-6 0.018380332716286137 2024-01-19T14:07:23Z JEFFERSON PARK DPR 1988-07-25T00:00:00Z
9 DPR N Queen Anne Boulevard 753 328 2004-04-09T00:00:00Z {3DA31C7B-C33D-4DD9-9092-71B1DA135BC8} 207890 PROPERTY-QABL QL-D1 2533300180 2024-01-19T14:07:23Z 5.790553187521671E-9 4.111515370853996E-4 2024-01-19T14:07:23Z QUEEN ANNE BOULEVARD DPR 1910-07-25T00:00:01Z
9 DPR N Queen Anne Boulevard 4688 328 2004-04-09T00:00:00Z {D3DA12EE-31E0-4476-95CE-1739E8FF8550} 207891 PROPERTY-QABL QL-D1 2533300180 2024-01-19T14:07:23Z 5.139665582905777E-9 3.9228813174494234E-4 2024-01-19T14:07:23Z QUEEN ANNE BOULEVARD DPR 1910-07-25T00:00:01Z
7 DPR N Ravenna Woods 15807 4411 2004-05-05T00:00:00Z {B1A2F3C6-C43A-4600-8064-A3DEFAE61DDC} 207892 PROPERTY-RAVWOOD QL-D1 0925049168 2024-01-19T14:07:23Z 5.585829095854119E-7 0.0030915009702507513 2024-01-19T14:07:23Z RAVENNA WOODS DPR 2002-07-02T00:00:00Z
9 DPR N Montlake Playfield 1507 376 1998-10-15T00:00:00Z {BF38EE1A-DBAB-400C-B083-B3F8B6A1EB39} 207893 PROPERTY-MTLKPF QL-D1 4089400080 2024-01-19T14:07:23Z 3.917576385699785E-7 0.0026768636765498316 2024-01-19T14:07:23Z MONTLAKE PLAYFIELD DPR 1933-12-31T00:00:01Z
9 DPR N Montlake Playfield 1507 376 2004-03-30T00:00:00Z {4CC47610-4927-465F-A346-04F66008D480} 207894 PROPERTY-MTLKPF QL-D1 6788202280 2024-01-19T14:07:23Z 1.1273410584671711E-6 0.004459900867682873 2024-01-19T14:07:23Z MONTLAKE PLAYFIELD DPR 1933-12-31T00:00:01Z
9 DPR N Montlake Playfield 1507 376 2004-03-30T00:00:00Z {10310B09-822A-4344-95CC-2D5D90C2466C} 207895 PROPERTY-MTLKPF QL-D1 6788202280 2024-01-19T14:07:23Z 1.4591960880701082E-6 0.006312257575513224 2024-01-19T14:07:23Z MONTLAKE PLAYFIELD DPR 1933-12-31T00:00:01Z
9 4357 PALATINE AVE N DPR N Fremont Peak Park 16454 4424 2004-10-06T00:00:00Z {A13D75B6-B355-4BDF-84CF-4D2838170201} 207896 PROPERTY-FRMPKPK QL-D1 6610000810 2024-01-19T14:07:23Z 6.118914081332531E-8 0.0011669443524576426 2024-01-19T14:07:23Z FREMONT PEAK PARK DPR 2004-09-30T00:00:00Z
9 DPR N SW Queen Anne Greenbelt 16459 234 2005-01-06T00:00:00Z {A13C70F6-F0AE-4E55-A12C-990BBC545F8D} 207897 PROPERTY-SWQAGRB QL-D1 7705100037 2024-01-19T14:07:23Z 5.565195258199712E-9 3.556012201607765E-4 2024-01-19T14:07:23Z SW QUEEN ANNE GREENBELT DPR 2004-11-04T00:00:00Z
3 9026 4TH AVE S DPR N Marra-Desimone Park 16455 4447 2007-02-21T00:00:00Z {E641A30D-FE4B-4B1D-9358-7F0F71324691} 207898 PROPERTY-MARDESPK QL-D1 3224049044 2024-01-19T14:07:23Z 2.211413981358282E-6 0.006388002898489655 2024-01-19T14:07:23Z MARRA-DESIMONE PARK DPR 2004-12-28T00:00:00Z
9 9026 4TH AVE S DPR N Marra-Desimone Park 16455 4447 2007-02-21T00:00:00Z {6A440787-F078-4DB9-9187-DA0E029A8E18} 207899 PROPERTY-MARDESPK QL-D1 3224049035 2024-01-19T14:07:23Z 1.981420659558925E-6 0.009774073825241834 2024-01-19T14:07:23Z MARRA-DESIMONE PARK DPR 2004-12-28T00:00:00Z
9 DPR Y Meadowbrook Playfield 803 352 {49C14F14-94FF-4080-9DE8-56EDF006145A} 207900 PROPERTY-MBPF QL-D1 2024-01-19T14:07:23Z 5.268695375108406E-7 0.00550990095454217 2024-01-19T14:07:23Z MEADOWBROOK PLAYFIELD SSD1
9 DPR N Meadowbrook Teen Life Center 352 {D099D793-CFEA-44E9-A57B-D8A1BA4525AF} 207901 PROPERTY-MBPF QL-D1 2024-01-19T14:07:23Z 8.504523207167994E-8 0.0011865488770908574 2024-01-19T14:07:23Z MEADOWBROOK TEEN LIFE CENTER SSD1

1.6 A Spatial index structure

We need an index structure to quickly match between the two sets of geometries.

See the JTS SearchUsingPreparedGeometryIndex tutorial.

(defn make-spatial-index [dataset & {:keys [geometry-column]
                                     :or   {geometry-column :geometry}}]
  (let [tree (org.locationtech.jts.index.strtree.STRtree.)]
    (doseq [row (tc/rows dataset :as-maps)]
      (let [geometry (row geometry-column)]
        (.insert tree
                 (.getEnvelopeInternal geometry)
                 (assoc row
                        :prepared-geometry
                        (org.locationtech.jts.geom.prep.PreparedGeometryFactory/prepare geometry)))))
    tree))
(def parks-index
  (make-spatial-index parks))
(defn intersecting-places [region spatial-index]
  (->> (.query spatial-index (.getEnvelopeInternal region))
       (filter (fn [row]
                 (.intersects (:prepared-geometry row) region)))
       tc/dataset))

For example, let us find the parks intersecting with the first neighborhood:

(delay
  (-> neighborhoods
      :geometry
      first
      (intersecting-places parks-index)
      (tc/select-columns [:PMA_NAME :NAME])))

_unnamed [2 2]:

:PMA_NAME :NAME
Salmon Bay Park SALMON BAY PARK
Loyal Heights Playfield LOYAL HEIGHTS PLAYFIELD

1.7 A Joined dataset

We compute a spatial join of the two datasets.

Note that even though many parks will appear as intersecting many neighbourhoods, this is not too memory-heavy, since they are references to the same map.

(def neighborhoods-with-parks
  (-> neighborhoods
      (tc/map-columns :parks
                      [:geometry]
                      #(intersecting-places % parks-index))))
(delay
  (-> neighborhoods-with-parks
      (tc/map-columns :n-parks
                      [:parks]
                      tc/row-count)
      (tc/select-columns [:L_HOOD :S_HOOD :n-parks])))

Seattle neighborhoods [94 3]:

:L_HOOD :S_HOOD :n-parks
Ballard Loyal Heights 2
Ballard Ballard 7
Ballard Whittier Heights 4
Ballard West Woodland 5
North Central Phinney Ridge 11
North Central Wallingford 27
North Central Fremont 15
North Central Green Lake 19
Northeast View Ridge 15
Northeast Ravenna 12
Northgate Pinehurst 8
Northwest North Beach/Blue Ridge 32
Northgate Licton Springs 6
Northgate Maple Leaf 18
Northwest Crown Hill 5
Northwest Greenwood 36
Ballard Sunset Hill 9
University District University District 11
University District University Heights 21
Downtown Denny Triangle 3
Greater Duwamish Industrial District 5

1.8 Computing areas

For every neighborhood, we will compute the proportion of its area covered by parks.

TODO: L_HOOD should be used, S_HOOD produces too many rows to be understood (maybe S_HOOD would be interesting for looking at one L_HOOD at a time)

(def neighborhoods-with-park-proportions
  (-> neighborhoods-with-parks
      (tc/map-columns :neighborhood-area
                      [:geometry]
                      area)
      (tc/map-columns :intersection-area
                      [:geometry :parks]
                      (fn [neigh-geometry parks]
                        (->> parks
                             :geometry
                             (map (fn [park-geometry]
                                    (area
                                     (.intersection (.buffer park-geometry 1)
                                                    neigh-geometry))))
                             fun/sum)))
      (tc/map-columns :park-names
                      [:parks]
                      (fn [parks]
                        (->> parks
                             :PMA_NAME
                             distinct
                             vec)))
      (tc/map-columns :park-names-str
                      [:park-names]
                      (partial str/join ","))
      (tc/add-column :park-proportion
                     #(fun// (:intersection-area %)
                             (:neighborhood-area %)))))
(delay
  (-> neighborhoods-with-park-proportions
      (tc/select-columns [:L_HOOD
                          :S_HOOD
                          :park-names
                          :neighborhood-area
                          :intersection-area
                          :park-proportion])
      (tc/order-by [:park-proportion] :desc)))

Seattle neighborhoods [94 6]:

:L_HOOD :S_HOOD :park-names :neighborhood-area :intersection-area :park-proportion
Northeast Sand Point [Warren G. Magnuson Park] 1.92498571E+07 1.26944073E+07 0.65945463
Magnolia Lawton Park [Magnolia Manor Park 5.50915253E+07 2.57689116E+07 0.46774729
Lawton Park
Discovery Park
Kiwanis Memorial Preserve Park
Discovery Park Boulevard
Discovery Park Tidelands
Kiwanis Ravine Overlook
Daybreak Star
Commodore Park]
Delridge North Delridge [Greg Davis Park 2.94546566E+07 1.32455699E+07 0.44969358
Cottage Grove Park
Rotary Viewpoint
Camp Long
West Seattle Golf Course
West Seattle Stadium
Longfellow Creek GS: North
Puget Boulevard
West Duwamish Greenbelt
Pigeon Point
Delridge Playfield
Westbridge Shops
WEST DUWAMISH GREENBELT]
North Central Green Lake [Ravenna Boulevard 3.73146298E+07 1.53146452E+07 0.41041932
Green Lake Park
Green Lake
Crescent Place
North Park Shops]
Seward Park Seward Park [Martha Washington Park 4.38352790E+07 1.32526645E+07 0.30232874
Seward Park
Lake Washington Boulevard
Lakewood Playground
Lakewood Moorage
Genesee Park and Playfield
Stan Sayres Memorial Park]
Delridge Riverview [Delridge and Myrtle 3.30382672E+07 8.76957592E+06 0.26543692
West Duwamish GS: Riverview
Riverview Playfield
West Duwamish GS: Puget Park
Puget Creek Edible Park
Puget Creek Greenspace
Puget Ridge Playground
Delridge Wetland
Puget Park
Puget Boulevard
Pigeon Point]
North Central Phinney Ridge [Cascade Place 3.21231179E+07 8.34975296E+06 0.25992972
Sunset Place
Rainier Place
Woodland Park Zoo
Woodland Park
Linden Orchard Park
Green Lake Park]
Northgate Pinehurst [Hubbard Homestead Park 2.94388509E+07 7.64686119E+06 0.25975407
Victory Creek Park
Pinehurst Playground
Flicker Haven Natural Area on Thornton Creek
Licorice Fern Natural Area on Thornton Creek
Jackson Park Golf Course]
Capitol Hill Montlake [Washington Park and Arboretum 2.14747867E+07 5.45298611E+06 0.25392504
Interlaken Park
Montlake Boulevard
Lake Washington Boulevard
East Montlake Park
Grand Army Cemetery
Montlake Playfield
Harvard-Miller/Roanoke Annex
West Montlake Park]
West Seattle Fauntleroy [Fauntleroy Park 3.38818639E+07 7.39981718E+06 0.21840053
Arroyos Natural Area
Stim Bullitt Natural Area
Endolyne Park
Fauntleroy Creek Ravine
Lincoln Park
Kilbourne Park
Solstice Park]
Delridge South Delridge [Longfellow Creek GS: South] 1.79233162E+07 1.32742172E+05 0.00740612
Queen Anne Lower Queen Anne [Blaine Place 1.75156245E+07 1.06952952E+05 0.00610615
Counterbalance Park
Kinnear Place
Kerry Park (Franklin Place)
Kerry Park and Viewpoint
Ward Springs Park]
Northwest Crown Hill [Crown Hill Glen Crown Hill Park] 1.58657504E+07 9.49434049E+04 0.00598417
University District University of Washington [Union Bay Boglands #2 Union Bay Boglands #1 Fritz Hedges Waterway Park] 2.55236751E+07 1.42125035E+05 0.00556836
Greater Duwamish Georgetown [Georgetown Pump Station Oxbow Park Georgetown Playfield] 5.13050726E+07 2.81438227E+05 0.00548558
Lake City Olympic Hills [Thornton Creek Addition: North Branch 2.58367422E+07 1.41195814E+05 0.00546492
Albert Davis Park
Little Brook Park]
West Seattle Genesee [West Seattle Junction 2.11649766E+07 9.18911781E+04 0.00434166
Junction Plaza Park
Ercolini Park
Dakota Place Park
48th Ave SW/SW Charlestown St
Fauntleroy Place]
West Seattle Seaview [Lowman Beach Park Morgan Junction Park] 1.84630618E+07 5.18603372E+04 0.00280887
Downtown Denny Triangle [McGraw Square Westlake Square Urban Triangle Park] 5.12817231E+06 1.16681726E+04 0.00227531
Greater Duwamish Harbor Island [] 1.78323007E+07 0.00000000E+00 0.00000000
Greater Duwamish SODO [] 4.96031676E+07 0.00000000E+00 0.00000000
(defn plot-neighborhoods-with-park-proportions [data]
  (kind/echarts
   {:title {:text "Neighborhoods by park proportion"}
    :tooltip {}
    :xAxis {:data (:S_HOOD data)
            :axisLabel {:rotate 90}}
    :yAxis {}
    :series [{:type "bar"
              :data (:park-proportion data)}]
    :grid {:containLabel true}}))
(delay
  (-> neighborhoods-with-park-proportions
      (tc/order-by [:park-proportion] :desc)
      plot-neighborhoods-with-park-proportions))

Note you may hover to see the neighborhood names. Let us take the ten most park-intense neighborhoods.

(delay
  (-> neighborhoods-with-park-proportions
      (tc/order-by [:park-proportion] :desc)
      (tc/head 10)
      plot-neighborhoods-with-park-proportions))

1.9 Representing park proportions as colors:

We will use Clojure2d’s clojure.color functionality.

(def gradient
  (color/gradient [:purple :yellow]))
(delay
  (-> 0.4
      gradient
      color/format-hex))
"#b3664d"

1.10 A choropleth coloured by park proportions

(def neighborhoods-coloured-by-park-proportion
  (-> neighborhoods-with-park-proportions
      (tc/map-columns :feature
                      [:feature :park-names-str :park-proportion]
                      (fn [feature park-names-str park-proportion]
                        (let [color (-> park-proportion
                                        gradient
                                        color/format-hex)]
                          (-> feature
                              (update-in
                               [:properties :style]
                               (fn [style]
                                 (-> style
                                     (assoc :color color
                                            :fillColor color
                                            :opacity 0.7
                                            :fillOpacity 0.7))))
                              (update-in
                               [:properties :tooltip]
                               str
                               (hiccup/html
                                [:div
                                 [:p [:b "Park proportion: "]
                                  (format "%.01f%%"
                                          (* 100 park-proportion))]
                                 [:p {:style {:max-width "200px"
                                              :text-wrap :wrap}}
                                  [:b "Parks: "]
                                  park-names-str]]))))))))
(delay
  (-> neighborhoods-coloured-by-park-proportion
      :feature
      vec
      Seattle-choropleth-map))

Another option: use opacity rather than color to higlight differences.

(def neighborhoods-highlighted-by-park-proportion
  (-> neighborhoods-coloured-by-park-proportion
      (tc/map-columns :feature
                      [:feature :park-proportion]
                      (fn [feature park-proportion]
                        (-> feature
                            (update-in
                             [:properties :style]
                             (fn [style]
                               (-> style
                                   (assoc :color "yellow"
                                          :opacity park-proportion
                                          :fillColor "yellow"
                                          :fillOpacity park-proportion)))))))))
(delay
  (-> neighborhoods-highlighted-by-park-proportion
      :feature
      vec
      Seattle-choropleth-map))